﻿package com.floorplanner.lib3ds
{	
	import flash.utils.*;
	
	public class Lib3dsFile extends Lib3dsCore
	{
		public var mesh_version:int;
		public var keyf_revision:int;
		public var name:String; // 12+1
		public var master_scale:Number;
		public var construction_plane:Array;
		public var ambient:Lib3dsRgba;
		public var shadow:Lib3dsShadow;
		public var background:Lib3dsBackground;
		public var atmosphere:Lib3dsAtmosphere;
		public var viewport:Lib3dsViewport;
		public var viewport_keyf:Lib3dsViewport;
		public var frames:int;
		public var segment_from:int;
		public var segment_to:int;
		public var current_frame:int;
		public var materials:Lib3dsMaterial;
		public var meshes:Lib3dsMesh;
		public var cameras:Lib3dsCamera;
		public var lights:Lib3dsLight;
		public var nodes:Lib3dsNode;
		
		public function Lib3dsFile()
		{
			//super( true );
		}
		
		/**
		 * 
		 * @return
		 */
		public function read( pIO:Lib3dsIO ):Boolean
		{			
			var result:Boolean = readFile( pIO );
			
			//this.nodes.print();
			
			return result;
		}
		
		/**
		 * 
		 * @return
		 */
		private function get numMaterials():uint 
		{
			var cnt:uint = 0;
			var p:Lib3dsMaterial = this.materials;
			while( p )
			{
				cnt++;
				p = p.next;
			}
			return cnt;
		}

		/**
		 * 
		 * @return
		 */
		private function get numMeshes():uint 
		{
			var cnt:uint = 0;
			var p:Lib3dsMesh = this.meshes;
			while( p )
			{
				cnt++;
				p = p.next;
			}
			return cnt;
		}
		
		/**
		 * read 3DS file.
		 * 
		 * @param	io
		 */
		private function readFile( io:Lib3dsIO ):Boolean
		{
			var c:Lib3dsChunk = Lib3dsChunk.read( io );
			var chunk:uint = 0;
			
			if( !c )
			{
				return false;
			}
			
			switch (c.chunk)
			{
				case Lib3dsChunk.MDATA:
					debug("-> MDATA");
					break;
				case Lib3dsChunk.MLIBMAGIC:
				case Lib3dsChunk.CMAGIC:
				case Lib3dsChunk.M3DMAGIC:
					debug("-> M3DMAGIC");
					chunk = c.next( io );
					while( chunk != 0 )
					{
						switch (chunk)
						{
							case Lib3dsChunk.M3D_VERSION:
								this.mesh_version = io.readDWord();
								debug("-> M3D_VERSION " + this.mesh_version);
								break;
							case Lib3dsChunk.MDATA:  // model data
								c.reset( io );
								debug(" -> MDATA");
								readMDATA( io );
								break;
							case Lib3dsChunk.KFDATA: // keyframe data
								debug("-> KFDATA");
								c.reset( io );
								//readKFDATA( io );
								break;
							default:
								debug("unknown chunk: 0x" + chunk.toString(16) );
								break;
						}
						chunk = c.next( io );
					} 
				default:
					break;
			}
						
			return true;
		}

		/**
		 * inserts a material.
		 * 
		 * @param	mat
		 */
		private function insertMaterial( material:Lib3dsMaterial ):void
		{
			var p:Lib3dsMaterial = null;
			var q:Lib3dsMaterial = null;
			
			for( p = this.materials; p != null; p = p.next ) 
			{
				if( material.name == p.name ) 
				{
					break;
				}
				q = p;
			}
			
			material.next = null;
			
			if(!q) 
			{
				this.materials = material;
			}
			else 
			{
				q.next = material;
			}		
		}

		/**
		 * inserts a mesh.
		 * 
		 * @param	mesh
		 */
		private function insertMesh( mesh:Lib3dsMesh ):void
		{
			var p:Lib3dsMesh = null;
			var q:Lib3dsMesh = null;
			
			for( p = this.meshes; p != null; p = p.next ) 
			{
				if( mesh.name == p.name ) 
				{
					break;
				}
				q = p;
			}
			
			mesh.next = null;
			
			if(!q) 
			{
				this.meshes = mesh;
			}
			else 
			{
				q.next = mesh;
			}		
		}
		
		/**
		 * find a node by id.
		 * 
		 * @return
		 */
		public function nodeById( node:Lib3dsNode, node_id:int ):Lib3dsNode
		{
			var p:Lib3dsNode, q:Lib3dsNode;
			
			for( p = node; p is Lib3dsNode; p = p.next )
			{
				if( p.node_id == node_id )
					return p;
					
				q = p.nodeById( node_id );
				if( q )
					return q;
			}
			
			return null;
		}
		
		/**
		 * inserts a node.
		 * 
		 * @param	node
		 */
		private function insertNode( file:Lib3dsFile, node:Lib3dsNode ):void
		{
			var p:Lib3dsNode = null;
			var n:Lib3dsNode = null;
			var parent:Lib3dsNode = null;
			
			if( !node || node.next || node.parent )
				throw new Error( "Cannot add to this node!" );
				
			if( node.parent_id != Lib3dsNode.NO_PARENT )
				parent = nodeById( file.nodes, node.parent_id );
	
			node.parent = parent;
			
			if( !parent )
			{
				p = null;
				n = this.nodes;
				for(; n; p = n, n = n.next) 
				{
					if( n.name == node.name )
						break;
				}
				if( !p ) 
				{
					node.next = this.nodes;
					this.nodes = node;
				}
				else
				{
					node.next = p.next;
					p.next = node;
				}
			}
			else
			{
				n = parent.childs;
				for( p = null; n; p = n, n = n.next ) 
				{
					if( n.name == node.name )
						break;
				}
				if( !p ) 
				{
					node.next = parent.childs;
					parent.childs = node;
				}
				else 
				{
					node.next = p.next;
					p.next = node;
				}
			}
			
			if( node.node_id != Lib3dsNode.NO_PARENT ) 
			{
				for( n = file.nodes; n is Lib3dsNode; n = p )
				{
					p = n.next;
					if(n.parent_id == node.node_id) 
					{
						removeNode(file, n);
						insertNode(file, n);
					}
				}
			}
		}
		
		/**
		 * 
		 * @param	file
		 * @param	node
		 * @return
		 */
		private function removeNode( file:Lib3dsFile, node:Lib3dsNode ):Boolean
		{
			var p:Lib3dsNode, n:Lib3dsNode;

			if( node.parent) 
			{
				for( p = null, n = node.parent.childs; n; p = n, n = n.next) 
				{
					if(n.name == node.name) 
					{
						break;
					}
				}
				if( !n ) 
				{
					return false;
				}

				if( !p ) 
				{
					node.parent.childs = n.next;
				}
				else 
				{
					p.next = n.next;
				}
			}
			else 
			{
				for( p = null, n = file.nodes; n; p = n, n = n.next) 
				{
					if(n.name == node.name) 
					{
						break;
					}
				}
				if( !n ) 
				{
					return false;
				}

				if( !p ) 
				{
					file.nodes = n.next;
				}
				else 
				{
					p.next = n.next;
				}
			}
			return true;
		}
		
		/**
		 * read keyframe data.
		 * 
		 * @return
		 */
		private function readKFDATA( io:Lib3dsIO ):Boolean
		{
			var c:Lib3dsChunk = Lib3dsChunk.read( io );
			var chunk:uint = 0;
			
			if( c.chunk != Lib3dsChunk.KFDATA )
			{
				return false;
			}
			
			debug( "reading KFDATA" );
			chunk = c.next( io );
			while( chunk != 0 )
			{
				switch (chunk)
				{
					case Lib3dsChunk.KFHDR:
						this.keyf_revision = io.readWord();
						this.name = io.readString(13);
						this.frames = io.readUnsignedInt();
						break;
						
					case Lib3dsChunk.KFSEG:
						this.segment_from = io.readUnsignedInt();
						this.segment_to = io.readUnsignedInt();
						debug( "from: " + this.segment_from + " to:" + this.segment_to );
						break;
						
					case Lib3dsChunk.KFCURTIME:
						this.current_frame = io.readUnsignedInt();
						break;
					
					case Lib3dsChunk.VIEWPORT_LAYOUT:
					case Lib3dsChunk.DEFAULT_VIEW:
						break;
					
					case Lib3dsChunk.OBJECT_NODE_TAG:
						var node:Lib3dsNode = new Lib3dsNode();
						node.type = Lib3dsNode.OBJECT_NODE;
						io.position -= 6;
						node.read( io )
						insertNode( this, node );
						break;
						
					default:
						break;
				}
				
				chunk = c.next( io );
			}
			
			return true;
		}
		
		/**
		 * read model data.
		 * 
		 * @return
		 */
		private function readMDATA( io:Lib3dsIO ):Boolean
		{
			var c:Lib3dsChunk = Lib3dsChunk.read( io );
			var chunk:uint = 0;
			
			if( c.chunk != Lib3dsChunk.MDATA )
			{
				return false;
			}
		
			chunk = c.next( io );
			while( chunk != 0 )
			{
				switch (chunk)
				{
					case Lib3dsChunk.MESH_VERSION:
						this.mesh_version = io.readInt();
						debug( "readMDATA MESH_VERSION -> " + this.mesh_version );
						break;
					case Lib3dsChunk.MASTER_SCALE:
						this.master_scale = io.readFloat();
						debug( "readMDATA MASTER_SCALE -> " + this.master_scale );
						break;
					case Lib3dsChunk.SHADOW_MAP_SIZE:
					case Lib3dsChunk.LO_SHADOW_BIAS:
					case Lib3dsChunk.HI_SHADOW_BIAS:
					case Lib3dsChunk.SHADOW_SAMPLES:
					case Lib3dsChunk.SHADOW_RANGE:
					case Lib3dsChunk.SHADOW_FILTER:
					case Lib3dsChunk.RAY_BIAS:
						c.reset( io );
						this.shadow = new Lib3dsShadow();
				
						break;
					case Lib3dsChunk.VIEWPORT_LAYOUT:
					case Lib3dsChunk.DEFAULT_VIEW:
						c.reset( io );
						this.viewport = new Lib3dsViewport()
						//this.viewport.read( io );
						break;
					case Lib3dsChunk.O_CONSTS:
						// construction plane
						this.construction_plane = io.readVector();
						debug( "CONSTRUCTION PLANE: " + this.construction_plane );
						break;
					case Lib3dsChunk.AMBIENT_LIGHT:
						c.reset( io );
						break;
					case Lib3dsChunk.BIT_MAP:
					case Lib3dsChunk.SOLID_BGND:
					case Lib3dsChunk.V_GRADIENT:
					case Lib3dsChunk.USE_BIT_MAP:
					case Lib3dsChunk.USE_SOLID_BGND:
					case Lib3dsChunk.USE_V_GRADIENT:
						debug( "parse BM" );
						break;
					case Lib3dsChunk.FOG:
					case Lib3dsChunk.LAYER_FOG:
					case Lib3dsChunk.DISTANCE_CUE:
					case Lib3dsChunk.USE_FOG:
					case Lib3dsChunk.USE_LAYER_FOG:
					case Lib3dsChunk.USE_DISTANCE_CUE:
						debug( "parse FOG" );
						break;
					case Lib3dsChunk.MAT_ENTRY:
						debug( "parse MAT_ENTRY" );
						c.reset( io );
						var mat:Lib3dsMaterial = new Lib3dsMaterial();
						if( mat.read( io ) )
						{
							insertMaterial( mat );
						}
						break;
					case Lib3dsChunk.NAMED_OBJECT:
						debug( "parse NAMED_OBJECT" );
						c.reset( io );
						readNamedObject( io );
						break;
					default:
						debug("parse_mdata -> unknown chunk: 0x" + chunk.toString(16) );
						break;	
				}
				chunk = c.next( io );
			}
			
			return true;
		}
		
		private function readNamedObject( io:Lib3dsIO ):Boolean
		{
			var c:Lib3dsChunk = Lib3dsChunk.read( io );
			var chunk:uint = 0;
			var name:String;
						
			if( c.chunk != Lib3dsChunk.NAMED_OBJECT )
			{
				throw new Error("readNamedObject => chunk is not of type NAMED_OBJECT!");
			}
			
			name = io.readString();			
			c.cur = io.position;
			
			chunk = c.next( io );
			
			while( chunk != 0 )
			{
				debug( "readNamedObject: " + name + " " + chunk);
				switch( chunk )
				{
					case Lib3dsChunk.N_TRI_OBJECT:
						c.reset( io );
						var mesh:Lib3dsMesh = new Lib3dsMesh();
						if( mesh.read( io, name ) )
						{
							this.insertMesh( mesh );
						}
						break;
					case Lib3dsChunk.N_CAMERA:
						debug("readNamedObject -> N_CAMERA");
						c.reset( io );
						//readCamera();
						break;
					case Lib3dsChunk.N_DIRECT_LIGHT:
						debug("readNamedObject -> N_DIRECT_LIGHT");
						break;
					default:
						debug("readNamedObject -> unknown chunk: " + c.chunk );
						break;
				}
				chunk = c.next( io );
			}
			
			c.readEnd( io );
			return true;			
		}
	}
}
